﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TLM.VsmdiReader;
using System.IO;
using Microsoft.VisualStudio.TestTools.Vsip;
using System.Linq;
using TLM.PlaylistGenerator.Model;
using System.Collections.Generic;
using System.Collections;

namespace TLM.PlaylistGenerator.Test
{
    [TestClass]
    public class GeneratorTest
    {
        const string ID_CONCAT = "TestID";
        string _testListsPath;
        string _testResultPath;
        ITestManagement _testManagementStub;

        [TestInitialize]
        public void InitializeVariables()
        {
            string assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
            _testListsPath = System.IO.Path.GetDirectoryName(assemblyLocation);

            // The generator uses the TestManagement and TMI to get the FQN of a test by it's Id.
            // Since we can't easily get a real TMI up and running we have to create a stub.
            // This also makes sure we have full control of the return values of the mocks.
            _testManagementStub = new Microsoft.VisualStudio.TestTools.Vsip.Fakes.StubITestManagement()
            {
                TmiInstanceGet = () =>
                {
                    return new Microsoft.VisualStudio.TestTools.Common.Fakes.StubITmi()
                    {
                        GetTestTestId = (testId) => 
                        { 
                            return new Microsoft.VisualStudio.TestTools.Common.Fakes.StubITestElement() 
                            { 
                                HumanReadableIdGet = () => 
                                { 
                                    return ID_CONCAT + testId.Id.ToString(); 
                                } 
                            }; 
                        }
                    };
                }
            };

            // The generator expects a path where it can save all the playlists
            string resultPath = Path.Combine(_testListsPath, @"GeneratorTestResults\");

            // If the directory already exists it has to be deleted (this can happen when the test is interrupted)
            if (Directory.Exists(resultPath))
            {
                Directory.Delete(resultPath, true);
            }

            _testResultPath = Directory.CreateDirectory(resultPath).FullName;
        }

        [TestMethod]
        public void PlaylistGenerator_ConvertTestListToPlaylist_FourTests_Test()
        {
            // Arrange
            string vsmdiPath = Path.Combine(_testListsPath, "VsmdiTestData", "FourTests.vsmdi");

            // Parse the vsmdi file with the test lists
            VsmdiReader.VsmdiParser parser = new VsmdiReader.VsmdiParser(vsmdiPath);
            VsmdiFile vsmdiFile = parser.ParseVsmdiFile();

            // Select a TestList which we want to convert and test
            TestList testList = vsmdiFile.ListOfTests.ChildLists.First();
            // The first list of FourTests should be available (sanity check)
            Assert.IsNotNull(testList);

            // Act
            int testsNotFound = 0;
            Playlist playlist = testList.ConvertToPlaylist(_testManagementStub.TmiInstance, ref testsNotFound);

            // Assert
            // Check for the right amount of included tests
            Assert.IsNotNull(playlist.IncludedTests);
            Assert.AreEqual(4, playlist.IncludedTests.Count);

            foreach (var testLink in testList.TestLinks)
            {
                Assert.IsTrue(playlist.IncludedTests.Any(it => it.FullyQualifiedName.Equals(ID_CONCAT + testLink.Id.ToString())));
            }

            Assert.AreEqual(0, testsNotFound);
        }

        [TestMethod]
        public void PlaylistGenerator_ConvertTestListToPlaylist_NestedLists_Test()
        {
            // Arrange
            string vsmdiPath = Path.Combine(_testListsPath, "VsmdiTestData", "NestedLists.vsmdi");

            // Parse the vsmdi file with the test lists
            VsmdiReader.VsmdiParser parser = new VsmdiReader.VsmdiParser(vsmdiPath);
            VsmdiFile vsmdiFile = parser.ParseVsmdiFile();

            // Act
            int testsNotFound = 0;
            Playlist playlist = vsmdiFile.ListOfTests.ConvertToPlaylist(_testManagementStub.TmiInstance, ref testsNotFound);

            // Assert (check if the right number of tests has been added)
            Assert.IsNotNull(playlist.IncludedTests);
            Assert.AreEqual(9, playlist.IncludedTests.Count);
        }

        [TestMethod]
        public void PlaylistGenerator_DisabledTestLink_Test()
        {
            // Arrange
            string vsmdiPath = Path.Combine(_testListsPath, "VsmdiTestData", "FourTestsDisabeldTestLink.vsmdi");

            // Parse the vsmdi file with the test lists
            VsmdiReader.VsmdiParser parser = new VsmdiReader.VsmdiParser(vsmdiPath);
            VsmdiFile vsmdiFile = parser.ParseVsmdiFile();

            // Act
            int testsNotFound = 0;
            Playlist playlist = vsmdiFile.ListOfTests.ConvertToPlaylist(_testManagementStub.TmiInstance, ref testsNotFound);

            // Assert (check if the right number of tests has been added)
            Assert.IsNotNull(playlist.IncludedTests);
            Assert.AreEqual(3, playlist.IncludedTests.Count);
        }

        [TestMethod]
        public void PlaylistGenerator_DisabeldTestList_Test()
        {
            // Arrange
            string vsmdiPath = Path.Combine(_testListsPath, "VsmdiTestData", "NestedListsDisabledTestList.vsmdi");

            // Parse the vsmdi file with the test lists
            VsmdiReader.VsmdiParser parser = new VsmdiReader.VsmdiParser(vsmdiPath);
            VsmdiFile vsmdiFile = parser.ParseVsmdiFile();

            // Act
            int testsNotFound = 0;
            Playlist playlist = vsmdiFile.ListOfTests.ConvertToPlaylist(_testManagementStub.TmiInstance, ref testsNotFound);

            // Assert (check if the right number of tests has been added)
            Assert.IsNotNull(playlist.IncludedTests);
            Assert.AreEqual(7, playlist.IncludedTests.Count);
        }

        [TestMethod]
        [Timeout(10000)]
        public void PlaylistGenerator_ConvertTestListToPlaylist_HugePlaylist_OnlyRoot_Test()
        {
            // Arrange
            const int FANWIDTH = 3;
            const int DEPTH = 10;
            const int TEST_LINKS_COUNT = 265719;

            VsmdiFile vsmdiFile = new VsmdiFile()
            {
                ListOfTests = CreateTestTestList(FANWIDTH, DEPTH)
            };

            // Act
            int testsNotFound = 0;
            Playlist playlist = vsmdiFile.ListOfTests.ConvertToPlaylist(_testManagementStub.TmiInstance, ref testsNotFound);

            // Assert
            Assert.IsNotNull(playlist.IncludedTests);
            Assert.AreEqual(TEST_LINKS_COUNT, AllTestLinks(vsmdiFile).Count());
            Assert.AreEqual(TEST_LINKS_COUNT, playlist.IncludedTests.Count);
        }

        [TestMethod]
        [Timeout(10000)]
        public void PlaylistGenerator_ConvertTestListToPlaylist_HugePlaylist_FullTree_Test()
        {
            // Arrange
            const int FANWIDTH = 4;
            const int DEPTH = 6;
            const int TEST_LINKS_COUNT = 21844;

            VsmdiFile vsmdiFile = new VsmdiFile()
            {
                ListOfTests = CreateTestTestList(FANWIDTH, DEPTH)
            };

            // Act
            Playlist playlist = GenerateAllPlaylists(vsmdiFile.ListOfTests);

            // Assert
            // Do a sanity check on the number of tests in the playlist
            Assert.AreEqual(TEST_LINKS_COUNT, AllTestLinks(vsmdiFile).Count());
            Assert.AreEqual(TEST_LINKS_COUNT, playlist.IncludedTests.Count);
        }

        private Playlist GenerateAllPlaylists(TestList list)
        {
            int testsNotFound = 0;
            Playlist playlist = list.ConvertToPlaylist(_testManagementStub.TmiInstance, ref testsNotFound);

            foreach (var childList in list.ChildLists)
            {
                GenerateAllPlaylists(childList);
            }

            Assert.AreEqual(list.GetAllTestLinks().Count(), playlist.IncludedTests.Count);

            return playlist;
        }

        /// <summary>
        /// Traverses the vsmdi file to retreive all test links
        /// </summary>
        /// <param name="file">the VSMDI file</param>
        /// <returns>the Test Lists</returns>
        private IEnumerable<TestLink> AllTestLinks(VsmdiFile file) 
        {
            Stack<TestList> todo = new Stack<TestList>();
            todo.Push(file.ListOfTests);
            while (todo.Count != 0)
            {
                var item = todo.Pop();
                foreach (var link in item.TestLinks)
                {
                    yield return link;
                }
                foreach (var list in item.ChildLists)
                {
                    todo.Push(list);
                }
            }
        }

        private TestList CreateTestTestList(int fanWidth, int depth)
        {
            TestList list = new TestList()
            {
                TestLinks = Enumerable.Repeat(new TestLink()
                {
                    Id = Guid.NewGuid(),
                    Name = Guid.NewGuid().ToString(),
                    Enabled = true,
                }, fanWidth).ToArray(),
                Id = Guid.NewGuid(),
                Name = Guid.NewGuid().ToString(),
                Enabled = true
            };

            if (depth > 0)
            {
                list.ChildLists = Enumerable.Repeat(CreateTestTestList(fanWidth, depth - 1)
                    , fanWidth).ToArray();
            }
            return list;
        }

        [TestCleanup]
        public void CleanupFolders()
        {
            // We have to cleanup the result folder after the test is completed
            Directory.Delete(_testResultPath, true);
        }
    }
}
